package TangramCore;

import TangramCore.Database.ObrazecData.HexadecInputStream;
import TangramCore.Database.ObrazecData.HexadecOutputStream;
import javax.swing.filechooser.FileFilter;
import java.awt.geom.Area;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class Skladacka {

  public Skladacka() {
  }

  public static final String defExt = ".skl", defSuperExt = ".sup";
  public String nazev, ext, fKeywords, sFile, sFileExt;
  public static final int MAXCOORD = 32;
  public double TOLERANCE_PRISKOK, TOLERANCE_RESENI, ROTACE_KROK, SCALE;
  public boolean SMART_ROTATION;

  public int pocetTypu;
  public int pocetDilku;
  public int uniqueDilek, needsFlip;
  private Dilek.Typ typy[];
  private Dilek.Typ typ[];
  private Poloha pTemp[];
  
  private boolean tangram, pentomino;
  private FileFilter fileFilter;
  private Skladacka superSet = null;
  
  /*public Skladacka(File setFile){
    this(setFile.getAbsolutePath());
  }*/
  private Skladacka(InputStream is) {
    try{
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader in = new BufferedReader(isr);
        //in = new BufferedReader(new FileReader(setFile));
        nazev = in.readLine().trim();

        ext = in.readLine().trim();
        fileFilter = new DatabaseFilter(ext, nazev + " Database");
        ext = "." + ext;
        fKeywords = in.readLine().trim();
        ROTACE_KROK = Double.parseDouble(in.readLine().trim());
        SMART_ROTATION = ROTACE_KROK < 0;
        ROTACE_KROK = Math.abs(ROTACE_KROK)*Math.PI/180;
        SCALE = Double.parseDouble(in.readLine().trim());
        TOLERANCE_PRISKOK = Double.parseDouble(in.readLine().trim())*SCALE;
        TOLERANCE_RESENI = Double.parseDouble(in.readLine().trim())*SCALE;
        if(TOLERANCE_RESENI<Obrys.TOLERANCE_AREA2)
            TOLERANCE_RESENI = Obrys.TOLERANCE_AREA2;
          //MAXCOORD = Integer.parseInt(in.readLine().trim());
        pocetTypu = Integer.parseInt(in.readLine().trim());
        typy = new Dilek.Typ[pocetTypu];
        pocetDilku = 0;

        tangram = nazev.toLowerCase().equals("tangram");
        pentomino = nazev.toLowerCase().equals("pentomino");

        for(int i = 0; i < pocetTypu; i++) {
            String nazev = in.readLine().trim();
            int pocetVyskytu = Integer.parseInt(in.readLine().trim());
            pocetDilku += pocetVyskytu;
            int pocetStran = Integer.parseInt(in.readLine().trim());
            Dilek.Typ t = new Dilek.Typ(i, nazev, pocetStran, pocetVyskytu);
            t.vrcholy = new double[t.pocetStran][2];
            t.deleni = new int[t.pocetStran];
            for(int j = 0; j < t.pocetStran; j++){
                String coord[] = in.readLine().split(",");
                t.vrcholy[j][0] = Double.parseDouble(coord[0].trim())*SCALE;
                t.vrcholy[j][1] = Double.parseDouble(coord[1].trim())*SCALE;
                t.deleni[j] = Integer.parseInt(coord[2].trim());
            }
            t.pOrig = new Polygon2D(t.newPoints());
            typy[i] = t;
            Dilek dTemp = new Dilek(this, -1, t);
            t.oDefault = new Obrys(new Area(dTemp));
            Obrys.compare(t.oDefault.od, t.oDefault.od, Obrys.TOLERANCE_AREA, t);
            t.getDefaultSortedSides();
        }

        pTemp = new Poloha[pocetDilku];
        typ = new Dilek.Typ[pocetDilku];
        uniqueDilek = needsFlip = -1;
        for(int i = 0, k = 0; i < pocetTypu; i++)
        for(int j = 0; j < typy[i].pocetVyskytu; j++){
          if(!typy[i].flipSymetry && needsFlip == -1)
            needsFlip = k;
          if(typy[i].pocetVyskytu == 1 && typy[i].rotationSymetrySides == typy[i].pocetStran && uniqueDilek == -1)
            uniqueDilek = k;
          typ[k++] = typy[i];
        }
        in.close();
        isr.close();
        is.close();
        if(uniqueDilek == -1){
        uniqueDilek = pocetDilku - 1;
        /*if(sFileExt.equalsIgnoreCase(defExt)) //DEBUG
            System.out.println(nazev + ": no suitable unique dilek, duplicity check is not reliable");
        */
        }
        if(needsFlip == -1)
        needsFlip = uniqueDilek;
    }catch(Exception e){
        //e.printStackTrace();
    	System.out.println("Could not load puzzle definition from " + sFile + "."); //DEBUG
    }
  }
  
  public String getDefDatabase(){
      return sFile.substring(0, sFile.length() - sFileExt.length()) + ext;
  }
              
  private boolean setSuperSet(Skladacka sklSup){
    superSet = sklSup;
    return sklSup != null;
  }
  
  public static Skladacka fromFile(String setFile, Class<? extends Object> cl){
    //String sss = System.getProperty("java.class.path");
    Skladacka skl = null;
    try{
        InputStream is = cl == null ? new FileInputStream(setFile) : cl.getResourceAsStream(setFile);
        skl = new Skladacka(is);
        if(skl.pocetDilku == 0)
            return null;
        skl.sFile = setFile;
        skl.sFileExt = setFile.substring(setFile.lastIndexOf('.'));
        skl.fKeywords = setFile.substring(0,setFile.lastIndexOf('/')+1) + skl.fKeywords;
        if(skl.sFileExt.equalsIgnoreCase(defExt)){
            String defSuper = skl.sFile.substring(0, skl.sFile.length() - skl.sFileExt.length()) + defSuperExt;
            skl.setSuperSet(Skladacka.fromFile(defSuper, cl));
        }
    }catch(FileNotFoundException ex){
        ex.printStackTrace();
    }
    return skl;
  }
  
  public static class DatabaseFilter extends FileFilter{
    private String ext, des;
    public DatabaseFilter(String e, String d){
        ext = "." + e;
        des = d + " (*" + ext +")";
    }
    @Override
    public boolean accept(File f){
        return f.isDirectory() || f.getName().endsWith(ext);
    }
    @Override    
    public String getDescription(){
        return des;
    }
    public File validate(File f){
      String s = f.getAbsolutePath();
      if(!s.endsWith(ext)){
        s += ext;
        return new File(s);
      }
      return f;
    }
  }
  public FileFilter getFileFilter(){
    return fileFilter;
  }

  public Dilek[] getDilky (){
    Dilek d[] = new Dilek[pocetDilku];
    for (int i = 0; i < pocetDilku; i++){
      d[i] = new Dilek(this, i, typ[i]);
    }
    return d;
  }

  public Dilek[] getDilky (Poloha p[]){
    Dilek d[] = new Dilek[pocetDilku];
    for (int i = 0; i < pocetDilku; i++){
      d[i] = new Dilek(this,i,typ[i], p[i]);
    }
    return d;
  }

  public Dilek[] getDilky (Dilek dil[]){
    Dilek d[] = new Dilek[pocetDilku];
    for (int i = 0; i < pocetDilku; i++){
      d[i] = new Dilek(dil[i]);
    }
    return d;
  }

  public String ulozObrazec(Dilek d[], Obrys.ObrysData od){
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    HexadecOutputStream hex = new HexadecOutputStream(out); 
    try{
        ulozObrazec(d, od, new DataOutputStream(hex));
    }catch(IOException ex){return "";}
    return out.toString();
      
  }
  
  public void ulozObrazec(Dilek d[], Obrys.ObrysData od, DataOutputStream out) throws IOException{
    for(int i = 0; i < pocetDilku; i++)
      d[i].getPoloha().saveToFile(out);
    if(od == null)
        out.writeInt(-1);
    else
        od.saveToFile(out);
  }

  public Dilek[] nactiObrazec(String data){
    ByteArrayInputStream in = new ByteArrayInputStream(data.getBytes());
    HexadecInputStream hex = new HexadecInputStream(in); 
    try{
        return nactiObrazec(new DataInputStream(hex));
    }catch(IOException ex){return null;}
  }
  
  public Dilek[] nactiObrazec(DataInputStream in) throws IOException {
      //Obrys.ObrysData od = new Obrys.ObrysData();
      Poloha p[] = new Poloha[pocetDilku];
      for(int i = 0; i<pocetDilku; i++){
          p[i] = new Poloha();
          p[i].readFromFile(in);
      }
    /*if(od != null)
      od.readFromFile(in);
    else
      odTrash.readFromFile(in);*/
    return getDilky(p);
  }

  public boolean isSameSolution(Dilek[] dsol, Dilek[] d, double tol, double tola) {
    for(int i=0; i<dsol.length; i++)
      pTemp[i] = dsol[i].getPoloha();
    return isSameSolution(pTemp, d, tol, tola);
  }

  public boolean isSameSolution(Poloha[] psol, Dilek[] d, double tol, double tola) {
    //pozor, manipuluje s dilky d => pokud je potreba je zachovat, nutno vytvorit kopii;
    Poloha psolu = psol[uniqueDilek];
    int flip = d[uniqueDilek].typ.flipSymetry ? 2 : 1;
    rotation:for (int r = 0; r < flip; r++) {
      Poloha pnewu = d[uniqueDilek].getPoloha();
      Point2D pivot = d[uniqueDilek].getCenter();
      if (psolu.flip != pnewu.flip || r == 1)
        Dilek.flipAll(d, pivot);
      d[uniqueDilek].normalize();
      double da = psolu.otoceni - pnewu.otoceni;
      double dx = psolu.posunX - pnewu.posunX;
      double dy = psolu.posunY - pnewu.posunY;
      for (int j = 0; j < d.length; j++) {
        d[j].otoc(pivot, da);
        d[j].posun(dx, dy);
        d[j].normalize();
      }
      //nyni by se u duplicatniho reseni mely shodovat polohy, az na mozne prohozeni dilku stejneho typu
      boolean used[] = new boolean[d.length];
      for (int j = 0; j < used.length; j++)
        used[j] = false;
      int pocetTypu = d[0].skladacka.pocetTypu;
      int dilek = 0;

      for (int j = 0; j < pocetTypu; j++) { //predpoklada, ze dilky stejneho typu jdou za sebou
        int pocetVyskytu = d[dilek].typ.pocetVyskytu;
        int nextType = dilek + pocetVyskytu;
        int start = dilek;
        for (; dilek < nextType; dilek++) {
          boolean fits = false;
          for (int poloha = start; poloha < nextType; poloha++) {
            if (used[poloha])
              continue;
            if (d[dilek].getPoloha().equals(psol[poloha], tol, tola)) {
              used[poloha] = true;
              fits = true;
              break;
            }
          }
          if (fits == false)
            continue rotation;
        }
      }
      return true;
    }
    return false;
  }

  public boolean isTangram(){
    return tangram;
  }
  public boolean isPentomino(){
    return pentomino;
  }
  public Skladacka getSuperSet(){
    return superSet;
  }
}
